package com.espirit.eap.sa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;

import com.espirit.eap.ValidationUtil;
import com.espirit.eap.manager.Department;
import com.googlecode.cswish.Config;
import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.annotation.ScopeValue;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.ParameterMap;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.security.SafeManager;
import com.googlecode.cswish.security.auth.UserInfo;
import com.googlecode.cswish.security.type.FreemarkerSQLRule;
import com.googlecode.cswish.security.type.PermissionRule;
import com.googlecode.cswish.struts.Action;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.Functions;
import com.googlecode.cswish.util.bean.ModelUtil;
import com.opensymphony.xwork2.Result;

//@Service
public class UserServiceSupport {

	private final static Log logger = LogFactory.getLog(UserServiceSupport.class);

	@Resource
	protected GenericService genericService;

	@Resource
	protected BeanIntrospector beanIntrospector;

	@Resource
	protected SafeManager safeManager;

	@Resource
	protected ObjectFactory objectFactory;

	@Resource
	protected Functions functions;

	@Resource
	protected Config config;

	@Resource
	protected CacheService cacheService;

	@PersistenceContext(unitName = "espirit")
	protected EntityManager em;
	
	@Resource
	protected LoginUserService loginUserService;

	@Resource
	protected WebsiteService websiteService;
	
	@Resource
	protected ValidationUtil validationUtil;

	private User adminUser;

	protected List<String> commonPermitPath;

	// NOTICE: this is hardcode logic for the special display field
	private Map<String, String> specialFieldPermDisplayRules;
	private FieldPermissionParser fieldPermissionParser;

	public int maxEnabledLoginCount;

	private UserServiceSupport userService;

	public UserServiceSupport getUserService() {
		if (userService == null) {
			userService = objectFactory.getBean(UserServiceSupport.class, false);
		}
		return userService;
	}

	public UserServiceSupport() {
		commonPermitPath = new ArrayList<String>();

		// mock a free path for all the @Rest(simpleServiceName="loginUser",
		// type="free")
		commonPermitPath.add("/loginUser.free.html");

		commonPermitPath.add("/espirit/i18n.html");
		commonPermitPath.add("/espirit/header.html");
		commonPermitPath.add("/espirit/test.html");

		commonPermitPath.add("/espirit/common/message.blank.html");
	}

	@PostConstruct
	private void init() {
		functions.registCommonService("user", this);

		//
		userCache = cacheService.newCache(String.class);
	}

	public User getLoginUser() {
		LoginUser loginUser = loginUserService.getLoginUser();
		User user = null;
		if (loginUser != null) {
			user = (User) loginUser.getUserEx();
			if (user == null) {
				user = getUserByLoginUser(loginUser.getId());
				loginUser.setUserEx(user);
			}
		}
		return user;
	}

	public boolean hasPermission(String actionName, String methodName) {
		return loginUserService.hasPermission(actionName, methodName);
	}

	public boolean hasLinkPermission(String link) {
		return hasLinkPermission(link, true);
	}

	public boolean hasLinkPermission(String link, boolean containBase) {
		return loginUserService.hasLinkPermission(link, containBase);
	}

	public List<Object> getRecordPermission(String link, boolean containBase, List<Object> data) {
		return loginUserService.getRecordPermission(link, containBase, data);
	}

	public List<String> filterLinkPermission(List<String> links, boolean keepEmptyLink) {
		return loginUserService.filterLinkPermission(links, keepEmptyLink);
	}

	public boolean hasLinkPermission(String link, String method) {
		return loginUserService.hasLinkPermission(link, method);
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public void _login(String productLogin, String productLoginDesc) {
		loginUserService._login(productLogin, productLoginDesc);
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public String login(String name, String password, boolean isEncrypted, String site) {
		loginUserService.login(name, password, isEncrypted, site);
		getLoginUser();
		// see user.login.html
//		String productName = ContextHolder.get().getProductName();
//		String homePage = productName + "/index.html";
//		return homePage;
		
		return "espirit/index.html";
	}

	public User getDefaultAdminUser() {
		if (adminUser != null) {
			return adminUser;
		}

		adminUser = new User();
		adminUser.setName("admin");
		User theUser = genericService.searchAll(adminUser).getData(0);
		if (theUser != null) {
			adminUser = theUser;
		}
		Website website = new Website();
		website.setCompId(Website.DEFAULT_SID);
//		website.setName(Website.DEFAULT_NAME);
		website = genericService.searchAll(website).getData(0);
		return adminUser;
	}

	// 自动权限1： privilege.mappingModelSimpleName(父)对应的权限
	// (注：privilege.modelSimpleName配置detail model权限)
	// 自动权限2： commonPermitPath
	// 权限范围： 页面, 页面字段, Model字段
	@Transactional
	public UserInfo loginUserBySite(String userId, String site) {
		UserInfo userInfo = loginUserService.loginUserBySite(userId, site, fieldPermissionParser);
		if (safeManager.isEnablePermissionCheck()) {
			buildRecordPrivilege(userInfo.getLoginId());
			for (String path : commonPermitPath) {
				safeManager.addPagePermission(userId, functions.linkNoBase(path, true));
			}
		}

		return userInfo;
	}

	private class FieldPermissionParser implements Executor<String, PathFieldPermission> {
		@Override
		public String execute(PathFieldPermission pathFieldPermission) {
			String path = pathFieldPermission.path;
			String fieldName = pathFieldPermission.fieldPermission.getFieldName();
			ScopeType scopeType = pathFieldPermission.fieldPermission.getScopeType();
			String condition = null;
			if (scopeType == null || scopeType.equals(ScopeType.Global)) {
				condition = null;// return fieldPermission.getCondition();
			} else {
				String forbidDisplayRule = specialFieldPermDisplayRules.get(path + '.' + fieldName);
				if (forbidDisplayRule == null) {
					forbidDisplayRule = "";
				}
				if (ScopeType.Basic.equals(scopeType)) {
					condition = "<#assign ownerId=fn.safeVal(model,'owner.id',0)><#if ownerId==0 || ownerId==user.userEx.id>${fn.s(fn.val(model,field_name)!,field_name,false)}<#else>"
							+ forbidDisplayRule + "</#if>";
				} else if (ScopeType.Local.equals(scopeType)) {
					condition = "<#assign ownerId=fn.safeVal(model,'owner.id',0)><#if ownerId==0 || ownerId==user.userEx.id || fn.contains(user.userEx.departmentIdList, fn.vals(model, 'owner.parents.partyParent.id'))>"
							+ "${fn.s(fn.val(model,field_name)!,field_name,false)}<#else>"
							+ forbidDisplayRule
							+ "</#if>";
				} else if (ScopeType.Deep.equals(scopeType)) {
					condition = "<#assign ownerId=fn.safeVal(model,'owner.id',0)><#if ownerId==0 || ownerId==user.userEx.id || fn.contains(user.userEx.descDepartmentIdList, fn.vals(model, 'owner.parents.partyParent.id'))>"
							+ "${fn.s(fn.val(model,field_name)!,field_name,false)}<#else>"
							+ forbidDisplayRule
							+ "</#if>";
				} else { // ScopeType.None
					condition = "";
				}
			}
			return condition;
		}
	}

	private void buildRecordPrivilege(String loginUserId) {
		List<Privilege> recordPrivileges = new ArrayList<Privilege>();
		LoginUser dbLoginUser = genericService.load(LoginUser.class, (loginUserId));
		for (Privilege privilege : dbLoginUser.getPagePermissions()) {
			// record limitation
			if (!ScopeType.Global.equals(privilege.getScopeType())) { // it's
																		// field
																		// permission
				recordPrivileges.add(privilege);
			}
		}

		User dbUser = getUserByLoginUser((loginUserId));
		if (dbUser == null) return ;
		if (dbUser.getEmployee() == null) return ;
		
		// 4. record limitation
		// record permission (such as, Customer.class, Supplier.class)
		// (canShowOthersSuppliers, canShowOthersCustomers)
		for (Privilege privilege : recordPrivileges) {
			// String path = functions.linkModelNoBase(modelName,
			// privilege.getButton().getMethod(), true);
			PermissionRule rule = new FreemarkerSQLRule();
			rule.setName("owner");
			String whereCondition = privilege.getCondition() == null ? null : privilege.getCondition();
			String condition = null;
			String fullName = beanIntrospector.getModelName(privilege.getModelSimpleName());
			Class clazz = beanIntrospector.getModelInfo(fullName).getTypeClass();

			if (clazz == null || privilege.getScopeType() == null)
				continue;
			if (!((LocalScopeAware.class.isAssignableFrom(clazz)) || (BasicScopeAware.class.isAssignableFrom(clazz)))) continue;
			
			boolean isDepartment = clazz.getSimpleName().equals(Department.class.getSimpleName());
			boolean isEmployee = clazz.getSimpleName().equals(Employee.class.getSimpleName());
			if (isDepartment){
				whereCondition = getDepartmentWhereCondtion(privilege.getScopeType(), "id", dbUser);
            	condition = getCondtion(privilege.getScopeType(), "id", dbUser);
			} else {
				if (whereCondition == null) {
	                if (DepartmentAware.class.isAssignableFrom(clazz)){
	                	whereCondition = getDepartmentWhereCondtion(privilege.getScopeType(), "department.id", dbUser);
	                	condition = getCondtion(privilege.getScopeType(), "department.id", dbUser);
					} 
	                String empWhere = null;
	                String empCondition = null;
					
					if (EmployeeAware.class.isAssignableFrom(clazz)){
						empWhere = "  a.employee.id=${user.userEx.employee.id?c} ";
						empCondition = buildCondition("fn.safeVal(model,'employee.id',0)==user.userEx.employee.id");
					}
					
					if (isEmployee){
						empWhere = "  a.id=${user.userEx.employee.id?c} ";
						empCondition = buildCondition("fn.safeVal(model,'id',0)==user.userEx.employee.id");
					}
					
					if (whereCondition == null && empWhere != null){
						whereCondition = empWhere;
						condition = empCondition;
					}
					if (whereCondition != null && empWhere == null){
						
					}
					if (whereCondition != null && empWhere != null){
						whereCondition += " or " + empWhere;
						condition += " || " + empCondition;
					}
				}
			}
			rule.setWhereCondition(whereCondition);
			rule.setCondition(condition);

			boolean addSuccess = addRecordLimit(loginUserId, privilege.getModelSimpleName(), privilege.getMethod(),
					rule);
			// add mapping model automaticall
			if (addSuccess && privilege.getMappingModelSimpleName() != null
					&& !privilege.getMappingModelSimpleName().equals(privilege.getModelSimpleName())) {
				addRecordLimit(loginUserId, privilege.getMappingModelSimpleName(), privilege.getMethod(), rule);
			}
		}
	}
	
	private String getDepartmentWhereCondtion(ScopeType scopeType, String property, User user){
		String userChildDeptIds = user.getChildDeptIds();
		String userChildrenDeptIds = user.getChildrenDeptIds();
		
		if (!StringUtils.isEmpty(userChildDeptIds)){
			if (ScopeType.Local.equals(scopeType)){
				return " a." + property + " in (" + userChildDeptIds + ") ";
			}
			if (ScopeType.Deep.equals(scopeType)){
				return " a." + property + " in (" + userChildrenDeptIds + ") ";
			}
		} else {
			return " a." + property + " in (0) ";
		}
		return null;
	}
	
	private String getCondtion(ScopeType scopeType, String property, User user){
		String userChildDeptIds = user.getChildDeptIds();
		
		if (!StringUtils.isEmpty(userChildDeptIds)){
			if (ScopeType.Local.equals(scopeType)){
				return buildCondition(" fn.contains(user.userEx.childDeptIds, fn.vals(model, " + property + "))"); 
			}
			if (ScopeType.Deep.equals(scopeType)){
				return buildCondition(" fn.contains(user.userEx.childrenDeptIds, fn.vals(model," + property + "))"); 
			}
		} else {
			return buildCondition(" fn.contains(user.userEx.departmentIds, 0)"); 
		}
		return null;
	}
	
	private String buildCondition(String str) {
		return "${(" + str + ")?string}";
	}

	// addRecordLimit is used by search method
	private boolean addRecordLimit(String userId, String modelSimpleName, String method, PermissionRule rule) {
		String path = functions.linkModelNoBase(modelSimpleName, method, true);
		if (safeManager.checkPage(userId, path)) {
			safeManager.addRecordLimit(userId, path, rule);
			return true;
		} else {
			return false;
		}
	}

	@Rest(simpleServiceName = "loginUser", type = "free")
	public Result logout() {
		return loginUserService.logout();
	}

	public void _add(
			User model,
			@ParamScope(visible = false, value = "#ps") PageElements ps,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		selectedPrivileges.set(null);
		initEditElements(ps);
	}

	public void add(
			User model,
			List<String> privileges,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		if (model.getLoginUser() == null) {
			model.setLoginUser(new LoginUser());
		}
		
		model.getLoginUser().setName(model.getCode());
		LoginUser loginUser = loginUserService.add(model.getLoginUser(), privileges, selectedPrivileges);
		model.setLoginUser(loginUser);
		model = genericService.merge(model);
		
		reCalcMap(model);
	}
	
	@Async
	public void reCalcMap(){
		User example = new User();
		List<User> results =genericService.searchAll(example).getData();
		for(User user : results){
			reCalcMap(user);
		}
	}
	
	private void reCalcMap(User model){
		if (model == null || model.getDeptIds() == null) return ;
		if (model.getDeptIds().isEmpty()){
			model.setChildDeptIds(null);
			model.setChildrenDeptIds(null);
			genericService.merge(model);
		} else {
			String[] deptIds = model.getDeptIds().split(",");
			Set<Integer> setChildIds = new HashSet<Integer>();
			Set<Integer> setChildrenIds = new HashSet<Integer>();
			for(String id : deptIds){
				Department dept = genericService.load(Department.class, Integer.parseInt(id));
				String childIds = dept.getChildIds();
				String[] ids = childIds.split(",");
				for(String deptId : ids){
					setChildIds.add(Integer.parseInt(deptId));
				}
				
				String childrenIds = dept.getChildrenIds();
				ids = childrenIds.split(",");
				for(String deptId : ids){
					setChildrenIds.add(Integer.parseInt(deptId));
				}
			}
			String childIds = getIdString(setChildIds);
			String childrenIds = getIdString(setChildrenIds);
			
			model.setChildDeptIds(childIds);
			model.setChildrenDeptIds(childrenIds);
			
			genericService.merge(model);
		}
		
	}
	
	private String getIdString(Set<Integer> setIds){
		StringBuilder sb = new StringBuilder();
		for(Integer id : setIds){
			sb.append(id.toString()).append(",");
		}
		String temp = sb.toString();
		String ids = temp.substring(0, temp.length() -1);
		return ids;
	}

	public Object _update(
			User model,
			@ParamScope(visible = false, value = "#ps") PageElements ps,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		selectedPrivileges.set(null);
		initEditElements(ps);
		genericService.view(model);
		
		// copy & clean the password
		LoginUser loginUser = new LoginUser();
		ModelUtil.copy(model.getLoginUser(), loginUser);
		loginUser.setPassword(null);
		model.setLoginUser(loginUser);
		return model;
	}

	private void initEditElements(@ParamScope(visible = false, value = "#ps") PageElements ps) {
		ps.setElementReadOnly("model.name", true);
		ps.setElementRequired("model.employee.id", false);
		ps.setElementVisible("model.name", false);
	}

	@Transactional
	public void update(
			User model,
			List<String> privileges,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		User dbUser = genericService.load(model);
		if (model.getLoginUser() == null) {
			model.setLoginUser(new LoginUser());
		}
		
		model.getLoginUser().setName(model.getCode());
		model.getLoginUser().setId(dbUser.getLoginUser().getId());

		LoginUser loginUser = loginUserService.update(model.getLoginUser(), privileges, selectedPrivileges);

		// update user base information
		model.setLoginUser(loginUser);
		if (model.getId() != null) {
			// update user base information
			Set<String> enabledFields = new HashSet<String>(Arrays.asList(new String[] { "code", "name",
					"partItemGrade.id", "tel", "email", "description", "parents.partyChild" }));

			dbUser = (User) genericService.update(model, true, enabledFields, null);
		} else {
			dbUser = genericService.add(model);
		}
		
		reCalcMap(dbUser);

	}

	public PageInfo search(ParameterMap model, int start, int limit, String sort, String dir, boolean newSearch,
			Reference<QLInfo> qlInfoRef, @ParamScope(visible = false, value = "#ps") PageElements ps) {
		initElements(ps);
		return genericService.search(model, start, limit, sort, dir, newSearch, qlInfoRef);
	}

	private void initElements(PageElements ps) {
		ps.setElementVisible("execute.callFlag", false);
		ps.setElementLength("execute.callFlag", 30);
		ps.setElementVisible("execute.callFlagAllocate", false);
	}

	@Rest(type = Action.SEARCH)
	public PageInfo<User> searchInDepartment(Department model) {
//		if (model.getId() == null || model.getId().intValue() == 0) {
//			return genericService.searchAll(new User());
//		}
//
//		model = genericService.load(model);
//		Set<Department> departments = model.getDescendentChildren(Department.class);
//		if (departments == null) {
//			departments = Collections.EMPTY_SET;
//		}
//		String ql = "select a from " + com.espirit.eap.sa.User.class.getName() + " a join a.parents b where b.partyParent.id in ("
//				+ StringUtils.repeat("?,", departments.size()) + "?)";
//		List<Integer> params = new ArrayList<Integer>(departments.size() + 1);
//		params.add(model.getId());
//		for (Department childDepartment : departments) {
//			params.add(childDepartment.getId());
//		}
//
//		// TODO : 1.remove duplicate entity 2.remove pageinfo limit.
//		return genericService.searchByQl(new QLInfo(ql, true, params.toArray(new Integer[params.size()])), false);
		
		return null;
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public void forgetPassword(String name) {
		loginUserService.forgetPassword(name);
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public void warning() {
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public Result resetPassword(UserPasswordReset model, String password) {
		return loginUserService.resetPassword(model, password);
	}

	@Rest(simpleServiceName = "loginUser", type = "free")
	public String _changePassword() {
		return loginUserService._changePassword();
	}

	@Rest(simpleServiceName = "loginUser", type = "free")
	public void changePassword(String verifyKey, String oldPassword, String password) {
		loginUserService.changePassword(verifyKey, oldPassword, password);
	}

	Map<String, String> userCache;

	public String getUserIdByCode(String code) {
		if (code == null)
			return null;
		if (userCache.get(code) == null) {
			User search = new User();
			search.setCode(code);
			User theUser = genericService.searchAll(search).getData(0);
			if (theUser == null) {
				return null;
			}
			userCache.put(code, theUser.getId());
		}
		return userCache.get(code);
	}

	public User getUserByLoginUser(String name) {
		String userId = getUserIdByCode(name);
		if (userId == null) return null;
		return genericService.load(User.class, userId);
	}

	private User getUserByLoginUser(Integer loginUserId) {
		String ql = "select a from " + User.class.getName() + " a where a.loginUser.id=?";
		User user = (User) validationUtil.searchAll(new QLInfo(ql, true, loginUserId)).getData(0);
		return user;
	}

	public List<User> getAuthUsers(Class clazz, String method) {
		String ql = "select distinct a.userTemplate from " + UserTemplatePrivilege.class.getName() + " a where a.privilege.modelSimpleName=? and a.privilege.method=? ";
		QLInfo qlInfo = new QLInfo(ql, true, this.beanIntrospector.getSimpleName(clazz.getName()), method);
		List<UserTemplate> userTemplates = (List<UserTemplate>) genericService.searchByQl(qlInfo, false).getData();
		List<User> results = new ArrayList<User>();
		if (userTemplates != null) {
			for (UserTemplate userTemplate : userTemplates) {
				if (userTemplate instanceof LoginUser) {
					LoginUser loginUser = (LoginUser) userTemplate;
					User user = getUserByLoginUser(loginUser.getId());
					results.add(user);
				}
			}
		}
		return results;
	}

}
